import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:convert' as convert;
import 'Log.dart';
import 'ProxyResults.dart';
//预检请求https://www.jianshu.com/p/0ac50bdf42aa
//https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS
//    print('请求方式：'+(request.headers[" Access-Control-Request-Method"] ).toString());
//    if (request.method == 'OPTIONS') {
////      //允许的源域名
////      //允许的请求类型
//////      request.response.headers.add("Access-Control-Allow-Methods","GET, POST, PUT,DELETE,OPTIONS,PATCH");
////      request.response.headers.add("Access-Control-Allow-Methods", '*');
////      request.response.headers.add("Access-Control-Allow-Credentials",  true);
////      request.response.headers.add("Access-Control-Allow-Headers",    request.headers['access-control-request-headers']);
////      request.response.cl ose();
////      return;
////    }  ;
/**dart httpserver 官方文档
 * https://dart.dev/tutorials/server/httpserver
 */
//请求次数
var requestCount = 0;
/**
 * 提交给目标服务器时候需要忽略的请求头参数 如果不忽略,服务器会返回:File not found.
 * 原因是:host是当前代理主机与端口,是由协议进行自动添加的， 如果这里指定host ,那么真是服务器可能会解析不到就会返回File not found.
 * 这里不应该自己手动指定,应该有http请求自动执行
 * http请求头host字段作用 ：
 * host是HTTP 1.1协议中新增的一个请求头字段，能够很好的解决一个ip地址对应多个域名的问题。*
 * 当服务器接收到来自浏览器的请求时，会根据请求头中的host字段访问哪个站点。
 *举个栗子，我有一台服务器A ip地址为120.79.92.223,有三个分别为www.baidu.com、www.google.com、www.sohu.com的域名解析到了这三个网站上，
 * 当我们通过http://www.baidu.com这个网址去访问时，DNS解析出的ip为120.79.92.223，
 * 这时候服务器就是根据请求头中的host字段选择使用www.baidu.com这个域名的网站程序对请求做响应
 */
const ignoreHeader = {
  "host": "127.0.0.1:4040",
};
const no_target_ip = 510; //没有目标地址
const proxy_requst_error = 511; //请求代理异常
const proxy_respones_error = 512; //代理响应异常
const proxy_error = 514; //代理相应错误
const Target_IP_Port = "src"; //放入到请求头的目标服务器地址

/// 转换Unicode 编码
String toUnicode(String args) {
  var bytes = utf8.encode(args);
  var urlBase = base64Encode(bytes);
  return utf8.decode(base64Decode(urlBase));
}

  main() async {
  try {
    run();
  } catch (e) {
    Log.e("代理系统异常", e);
    print(e);
  }
}

/**
 * 获取服务端地址
 */
Uri getServerAddress(HttpRequest request) {
  if (request.uri.queryParameters.containsKey(Target_IP_Port)) {
    var url=   request.uri.queryParameters[Target_IP_Port];
    url =request.uri.query.replaceFirst(Target_IP_Port+"=", "");
    // Log.d("他娘的:", request.uri.queryParametersAll.toString() );
    // Log.d("他娘的:", request.uri.queryParameters.toString() );
    // Log.d("他娘的:", request.uri.query.toString() );
    var uri = Uri.parse(url);
    return uri;
  }
  var targetIp = request.headers.value(Target_IP_Port).toString();
  var uri = Uri.parse(targetIp);
  //转换成uri注意：这里如果携带端口号，则一定要携带scheme 否则会返回异常
  //请求地址拼接修改
  var proxyRequestUri = uri.resolve(request.uri.toString());
  return proxyRequestUri;
}

void run() async {

  var server = await HttpServer.bind(InternetAddress.anyIPv4, 4040);
//  https 相关绑定：http://www.voidcn.com/article/p-vtuzytxd-btp.html
//  var httpsServer = await HttpServer.bindSecure(InternetAddress.anyIPv4, 4040);
  Log.d('代理请求端口', '${server.port} ');
  server.defaultResponseHeaders.add('Access-Control-Allow-Origin', '*'); //允许跨域
  server.defaultResponseHeaders
      .add("Access-Control-Allow-Methods", '*'); //跨域预检请求时候允许的请求方式
  server.defaultResponseHeaders
      .add("Access-Control-Allow-Headers", "*"); //允许跨域自定义的请求头
  server.defaultResponseHeaders.add("Access-Control-Allow-Credentials",
      true); //如果服务器端的响应中未携带 Access-Control-Allow-Credentials: true ，浏览器将不会把响应内容返回给请求的发送者。
  server.defaultResponseHeaders   .add("Access-Control-Max-Age", "3600"); //跨域时候预检周期，防止重复性预检

  await for (HttpRequest request in server) {
    requestCount++;
    var tmpReqTag = "请求id:" + requestCount.toString();
    Log.i("☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆", tmpReqTag.toString());
    //      var errorCode;
//      var errorReason;
//      var errormsg ={"code": errorCode,"message":errorReason};
    try {
      if (request.method == "OPTIONS") {
        Log.d(tmpReqTag, "处理预检请求");
//      print("-------------------------预检请求头-------------------------");
//      print(request.headers);
        request.response
          ..write("预检完成")
          ..close();
        continue;
      }
      Log.d(tmpReqTag, request.uri.queryParameters[Target_IP_Port]);
      Log.w("收到请求全路径:",   request.uri.toString());
      Log.w("全部URL参数:",    request.uri.queryParameters.toString());
      if ( !request.uri.queryParameters.containsKey(Target_IP_Port)) {
        Log.w(tmpReqTag, "url中未携带" + Target_IP_Port+"无法代理请求目标服务器) ");
       var      deails= "欢迎使用动态代理服务 \n错误原因:请求头或url中未携带"       + Target_IP_Port+
          "无法代理请求目标服务器\n使用方法: \n   url面增加 src 参数作为目标服务器地址请求的连接,体参直接放入到当前请求即可\n";
      String str=   convert.jsonEncode(new ProxyResults(no_target_ip,msg: "代理服务无法识别您的请求",details: deails));
       request.response
          ..statusCode = no_target_ip
          ..write( str)   ..close();
        continue;
      }
      //异步处理
      processing(tmpReqTag, request);
    } catch (e, s) {
      String str=   convert.jsonEncode(new ProxyResults(proxy_error,msg: e.toString(),details: s.toString()));
      request.response.statusCode = proxy_error;
      request.response
        ..write(str)
        ..close();
      Log.e(tmpReqTag, '发生异常 ${e} \n 堆栈信息:\n ${s}.');
      continue;
    }
  }
}

/**
 * 处理请求,通过async 增加异步可以提高请求并发量级
 */
void processing(tmpReqTag, request) async {
  getBodyContent(request).then((String value) {
    try {
      pirntRequest(tmpReqTag, request, value);
      //目标地址IP端口号
      var proxyRequestUri = getServerAddress(request);
      if (proxyRequestUri.scheme == null||proxyRequestUri.scheme.isEmpty) {
        String str=   convert.jsonEncode(new ProxyResults(proxy_error,msg:'未指定协议',details:  '未指定协议:例如:http://www.baidu.com'));
        request.response.statusCode = proxy_error;
        request.response.statusCode = proxy_error;
        request.response
          ..write(str)
          ..close();
       return null;
      }
      proxyRequst(tmpReqTag, request, proxyRequestUri, value);
    } catch (e, s) {
      Log.e(tmpReqTag, '发生异常 ${e} \n堆栈信息:\n  ${s}.');
      String str=   convert.jsonEncode(new ProxyResults(proxy_error,msg: e.toString(),details:s.toString()));
      request.response.statusCode = proxy_error;
      request.response
        ..write(str)
        ..close();
    }
  }
  );
}

/**
 * 执行请求
 */
void proxyRequst(String tmpReqTag, final HttpRequest request,   Uri proxyRequestUri, String value)  async {
  var proxyHttpClient   = new HttpClient();
  try {

    await proxyHttpClient. openUrl(request.method, proxyRequestUri)
      // Makes a request to the external server.向外部服务器发出请求。
      //.then((HttpClientRequest proxyRequest) => proxyRequest.close())
    .then((HttpClientRequest proxyRequest) {
        try {
          request.headers.forEach((name, values) {
            if (!ignoreHeader.containsKey(name)) {
              proxyRequest.headers.add(name, values);
            }
          });
          Log.d(tmpReqTag,     "-----------发送给服务器请求头------------\n${proxyRequest.headers}");
          //注意：value 是客户端传过来的，在读取时候一定要用iso8859-1读取，因为write 写入 用的就是iso8859否则中文就异常退出了
          proxyRequest.write(value);
        } catch (e, s) {
          Log.d(tmpReqTag, '错误详情:\n $e  堆栈信息:\n $s');
          String str=   convert.jsonEncode(new ProxyResults(proxy_requst_error,msg: e.toString(),details:s.toString()));
          request.response.statusCode = proxy_requst_error;
          request.response
            ..write(str)
            ..close();
          print(e);
        }
        return proxyRequest.close();
      }
//      ,onError: (){
//      Log.d(tmpReqTag,     "-----------擦擦擦------------");
//        request.response
//          ..statusCode = proxy_requst_error
//          ..close();
//      }
      )
//        .catchError((){
//      Log.i(tmpReqTag, "------------------bbd---------------------");
//    }
//    )
        .then((HttpClientResponse proxyResponse) {
        Log.i(tmpReqTag,   "------------------响应头--------------------\n${proxyResponse  .headers}");
        proxyResponse.transform(convert.utf8.decoder).join().then((
            String value) {
          Log.i(tmpReqTag,  "------------------响应内容---------------------\n${value}");
          request.response
            ..statusCode = proxyResponse.statusCode
            ..write(value)
            ..close();
        });
      }
//      , onError: () {
//        request.response
//          ..statusCode = proxy_respones_error
//          ..close();
//        Log.i(tmpReqTag, "------------------响应异常---------------------");
//      }
      )
//        .catchError((){
//      Log.i(tmpReqTag, "------------------bbd---------------------");
//    })
        .whenComplete(() => {
//      Log.i(tmpReqTag, "------------------请求执行完成---------------------")
    }
    );
  } on TimeoutException catch (e,s) {
    String str=   convert.jsonEncode(new ProxyResults(proxy_requst_error,msg: '请求超时',details:e.toString()));
    request.response.statusCode = proxy_requst_error;
    request.response
      ..write(str)
      ..close();
    proxyHttpClient.close(force: true);
  }
  on SocketException catch (e) {
    String str=   convert.jsonEncode(new ProxyResults(proxy_requst_error,msg: '连接服务失败',details:e.toString()));
    request.response.statusCode = proxy_requst_error;
    request.response
      ..write(str)
      ..close();
    proxyHttpClient.close(force: true);
  }  on Exception catch (e, s) {

    Log.i(tmpReqTag, "------------------请求异常---------------------");
    Log.i(tmpReqTag, e.toString());
    String str=   convert.jsonEncode(new ProxyResults(proxy_error,msg: '请求异常',details:e.toString()));
    request.response.statusCode = proxy_error;
    request.response
      ..write(str)
      ..close();
    proxyHttpClient.close(force: true);
  }
}

/**
 * 打印请求信息
 */
void pirntRequest(tmpReqTag, request, String value) {
  Log.i(tmpReqTag.toString(), request.method);
  Log.i(tmpReqTag.toString(), "-----------请求头------------\n${request.headers}");
  Log.i(tmpReqTag.toString(), '目标地址：' + getServerAddress(request).toString());
  Log.i(tmpReqTag.toString(), "--------请求URL参数----------- ");
  request.uri.queryParameters.forEach((param, val) {
    Log.i(tmpReqTag.toString(), param + ':' + val);
  });
  //字符编码转换原来是iso8859-1 现转换成utf-8方便打印日志查看
  Log.i(tmpReqTag.toString(),     "---------体参数-------------\n${utf8.decode(value.codeUnits)}");
}

/**
 *  获取表单的数据，以下代码参考，感谢大神
 *  http://www.cndartlang.com/844.html
 * 获取post的内容
 */
Future<String> getBodyContent(HttpRequest request) async {
  /**
   * Post方法稍微麻烦一点
   * 首先，request传送的数据时经过压缩的
   * index.html中设置了utf8，因此需要UTF8解码器
   * 表单提交的变量和值的字符串结构为：key=value
   * 如果表单提交了多个数据，用'&'对参数进行连接
   * 对于提取变量的值，可以自行对字符串进行分析
   * 不过也有取巧的办法：
   * Uri.queryParameters(String key)能解析'key=value'类型的字符串
   * Uri功能很完善，协议、主机、端口、参数都能简单地获取到
   * 其中，uri参数是用'?'连接的，例如：
   * http://www.baidu.com/s?wd=dart&ie=UTF-8
   * 因此，为了Uri类能正确解析，需要在表单数据字符串前加'?'
   */
  var encodingName = Encoding.getByName("iso_8859-1");
  String strRaw =
  //    await utf8.decoder.bind(request).join("&"); //重点：dart2用的UTF8这里补鞥用需要用这种方式  ，另外这里要用ISO 8859 -1方式获取，要不然HttpClientRequest.write() 写入服务器时候无法转换字符，从而失败
  await encodingName.decoder.bind(request).join("&");
//  print('-----------------体参数原始数据-------------------------------');
  return strRaw;
 }
/**
 * post 内容转换为KeyValue方便获取
 * 这里不能转换form-data 格式
 */
stringBody2KV(String strRaw) {
  //这里原始数据是{"name":"typeText"} 或者accessKey=队长是我&password=4555，下面通过增加？ 然后通过uri通过的参数查询进行获取方便获取+
  print(strRaw);
  try {
    String strUri = "?" + Uri.decodeComponent(strRaw);
    return Uri.parse(strUri).queryParameters;
  } catch (e) {}
  return null;
}
